/**
 * Socket code
 * (C) Nettle developers 2000-2004
 *
 * $Id: socket,v 1.39 2004/04/13 20:30:56 archifishal Exp $
 */

#include "generic.h"
#include "globals.h"

#include <time.h>

#include "sys/types.h"

#include "sys/ioctl.h"
#include "sys/socket.h"
#include "socklib.h"
#include "inetlib.h"
#include "sys/errno.h"
#include "sys/time.h"

#include "netinet/in.h"
#include "arpa/inet.h"

#include "netdb.h"

#include "socket.h"
#include "sockwatch.h"

#define ioctl socketioctl
#define close socketclose

int socket_connecttoip(unsigned long ip_address,int port_number)
{
  int socket_handle;
  int data=1;
  struct sockaddr_in address;

  socket_handle=socket(AF_INET,SOCK_STREAM, PF_UNSPEC);
  if (socket_handle==-1)
    return -1;

  if (ioctl(socket_handle,FIONBIO,&data))
  {
    close(socket_handle);
    return -1;
  }

  data=1;
  if (setsockopt(socket_handle, SOL_SOCKET, SO_KEEPALIVE, (char *) &data, sizeof(data)))
  {
    close(socket_handle);
    return -1;
  }

  socketwatch_register(socket_handle);

  address.sin_family=AF_INET;
  address.sin_port=htons(port_number);
  address.sin_addr.s_addr=ip_address;

  connect(socket_handle,(struct sockaddr *) &address, sizeof(address));

  return socket_handle;
}



void socket_close(int socket_handle)
{
  /* Check socket_handle is sane before passing it here */
  if (socket_handle!=-1)
  {
    socketwatch_deregister(socket_handle);
    close(socket_handle);
  }
}



int socket_connected(int socket_handle)
{
  fd_set            fds;
  struct timeval    timeout;
  int               numready;

  /* select() on the socket, see if it is ready for writing.
   * Conversely to what you might expect, a socket that has failed to
   * connect with be flagged as ready for writing.
   */
  timerclear(&timeout); /* no timeout */
  FD_ZERO(&fds);
  FD_SET(socket_handle, &fds);
  numready = select(socket_handle+1, NULL, &fds, NULL, &timeout);

  if (numready == -1 || (numready > 0 && FD_ISSET(socket_handle, &fds)))
  {
    /* either select returned an error (so we look at the socket to
     * see if it's even valid anymore), or select said the socket is
     * ready for writing.
     */
    int result;
    int size = sizeof(result);

    /* read the pending socket error in 'result' */
    if (getsockopt(socket_handle, SOL_SOCKET, SO_ERROR,
                   (char *) &result, &size) == -1)
    {
      /* error reading error number from socket - give up */
      return errno;
    }

    if (result == 0)
    {
      /* no error => connected! */
      return 0;
    }

    /* otherwise, an error occurred - return the error number */
    return result;
  }

  /* select succeeded & socket not ready for writing => still connecting */
  return -1;
}



int socket_readdata(int socket_handle, char * buffer, int length)
{
  int result;

  result=recv(socket_handle,buffer,length,0);

  if (result==-1)
    return 0;

  if (result==0)
    return -1;

  return result;
}


int socket_senddata(int socket_handle, const char * buffer, int length)
{
  int result;

  result = send(socket_handle, (void *) buffer, length, 0);

  if (result==-1)
  {
    if (errno == EWOULDBLOCK)
    {
      /* We just wait for some time when we would not block */
      return 0;
    }
    else
    {
      printf("errno for socket_senddata was: %d (for sending %p, %d)\n",errno, buffer, length);
    }
  }

  return result;
}


char *socket_ip_string(unsigned long address)
{
  struct in_addr saddr;

  saddr.s_addr = address;

  return inet_ntoa(saddr);
}

int socket_port_number_from_name(const char *port_name)
{
  struct servent *port_lookup = NULL;

  port_lookup = getservbyname(port_name, "tcp");
  if (port_lookup != NULL)
  {
    return ntohs(port_lookup->s_port);
  }

  return 0;
}

const char *socket_strerror(int errnum)
{
  static char ueb[64];

  switch (errnum)
  {
    case 0:
      return "No error";

    default:
      sprintf(ueb,"Unknown error %d", errnum);
      return ueb;

    case ENOENT               : return "Not found"; /* 2 */
    case EBADF                : return "Invalid descriptor"; /* 9 */
    /*case EAGAIN               : return "No more ports";*/ /* 11 */
    case EFAULT               : return "Bad address"; /* 14 */
    case EINVAL               : return "Invalid argument"; /* 22 */
    case ENOSPC               : return "No space on device/DNS buffer overflow"; /* 28 */
    case EPIPE		          : return "Broken pipe"; /* 32 */
    case EWOULDBLOCK          : return "Operation would block"; /* 35 */
    case EINPROGRESS          : return "Operation now in progress"; /* 36 */
    case EALREADY             : return "Operation already in progress"; /* 37 */
    case ENOTSOCK             : return "Socket operation on non-socket"; /* 38 */
    case EDESTADDRREQ         : return "Destination address required"; /* 39 */
    case EMSGSIZE             : return "Message too long"; /* 40 */
    case EPROTOTYPE           : return "Protocol wrong type for socket";
    case ENOPROTOOPT          : return "Protocol not available"; /* 42 */
    case EPROTONOSUPPORT      : return "Protocol not supported"; /* 43 */
    case ESOCKTNOSUPPORT      : return "Socket type not supported"; /* 44 */
    case EOPNOTSUPP           : return "Operation not supported on socket"; /* 45 */
    case EPFNOSUPPORT         : return "Protocol family not supported";  /* 46 */
    case EAFNOSUPPORT         : return "Address family not supported by protocol family"; /* 47 */
    case EADDRINUSE           : return "Address already in use"; /* 48 */
    case EADDRNOTAVAIL        : return "Can't assign requested address"; /* 49 */
    case ENETDOWN             : return "Network is down"; /*/ 50 */
    case ENETUNREACH          : return "Network is unreachable"; /*/ 51 */
    case ENETRESET            : return "Network dropped connection on reset"; /*/ 52 */
    case ECONNABORTED         : return "Software caused connection abort"; /*/ 53 */
    case ECONNRESET           : return "Connection reset by peer"; /*/ 54 */
    case ENOBUFS              : return "No buffer space available"; /*/ 55 */
    case EISCONN              : return "Socket is already connected"; /*/ 56 */
    case ENOTCONN             : return "Socket is not connected"; /*/ 57 */
    case ESHUTDOWN            : return "Can't send after socket shutdown"; /*/ 58  */
    case ETOOMANYREFS         : return "Too many references: can't splice"; /*/ 59  */
    case ETIMEDOUT            : return "Connection timed out"; /*/ 60 */
    case ECONNREFUSED         : return "Connection refused"; /*/ 61  */
    case EHOSTDOWN            : return "Host is down"; /*/ 64 */
    case EHOSTUNREACH         : return "No route to host"; /*/ 65 */
    case 486                  : return "No internet stack available";
  }
}
